package com.xyzwps.webfluxjava.handler.demo;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.xyzwps.webfluxjava.common.exception.BizException;
import com.xyzwps.webfluxjava.common.exception.MyErrorAttributes;
import com.xyzwps.webfluxjava.dao.repository.ArticleRepository;
import com.xyzwps.webfluxjava.handler.RoutersConfig;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import static org.springframework.web.reactive.function.server.ServerResponse.ok;
import static org.springframework.web.reactive.function.server.ServerResponse.notFound;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;

@Component
@Slf4j
public class DemoHandlers {
	
	@Autowired
	private ArticleRepository articleRepository;

    /**
     * 演示普通的 get 请求
     */
    public Mono<ServerResponse> getJSON(ServerRequest req) {
        val body = ImmutableMap.of("hello", "world", "path", req.path());
        return ok().contentType(APPLICATION_JSON_UTF8).body(fromObject(body));
    }

    /**
     * 演示路径参数
     * <p>
     * 可以看到，这里只能自己去搞类型转换了
     */
    public Mono<ServerResponse> pathVars(ServerRequest req) {
        val body = ImmutableMap.of("id", req.pathVariable("id"));
        return ok().contentType(APPLICATION_JSON_UTF8).body(fromObject(body));
    }

    /**
     * 演示 filter 的使用
     * <p>
     * 结合 {@link RoutersConfig#routers()} 来查filter 是如何使用的
     */
    public Mono<ServerResponse> forfilters(ServerRequest req) {
        log.info("do request");
        val a1 = req.attribute("filter1");
        val a2 = req.attribute("filter2");
        val a3 = req.attribute("filter3");
        val body = ImmutableMap.of(
                "a1", a1.orElse("no a1"),
                "a2", a2.orElse("no a2"),
                "a3", a3.orElse("no a3")
        );
        return ok().contentType(APPLICATION_JSON_UTF8).body(fromObject(body));
    }

    /**
     * 演示如何处理 post 请求，同时演示返回数据的其他方法
     */
    public Mono<ServerResponse> post(ServerRequest req) {
        Mono<Map<String, Object>> mono = req.bodyToMono(RequestDto.class)
                .map(body -> ImmutableMap.of("reqdto", body, "extra", "whatever"));
        return ok().body(mono, new ParameterizedTypeReference<Map<String, Object>>() {});
    }

    /**
     * 演示1个异步任
     */
    public Mono<ServerResponse> async(ServerRequest req) {
        Mono<Map<String, Object>> mono = req.bodyToMono(RequestDto.class)
                .flatMap(this::toListBlocking)
                .publishOn(Schedulers.elastic())
                .map(list -> ImmutableMap.of(
                        "list", list,
                        "threadName", Thread.currentThread().getName(),
                        "threadId", Thread.currentThread().getId()
                ));
        return ok().body(mono, new ParameterizedTypeReference<Map<String, Object>>() {});
    }

    private Mono<List<?>> toListBlocking(RequestDto dto) {
        return Mono.fromCallable(() -> {
            Thread.sleep(1000);
            return ImmutableList.of(dto.getId(), dto.getStatus(), "装模作样阻塞 1 ",
                    Thread.currentThread().getName(), Thread.currentThread().getId());
        });
    }

    /**
     * 演示两个异步的任
     */
    public Mono<ServerResponse> parallelAsync(ServerRequest req) {

        Mono<JSONObject> mono = req.bodyToMono(RequestDto.class)
                .flatMap(this::toListBlocking)
                .map(list -> {
                    val json = new JSONObject();
                    json.put("list", list);
                    val t = Thread.currentThread();
                    json.put("thread", t.getName() + " = " + t.getId());
                    return json;
                });

        Mono<JSONObject> mono2 = Mono.just(new JSONObject())
                .map(json -> {
                    val t = Thread.currentThread();
                    json.put("thread", t.getName() + " = " + t.getId());
                    return json;
                });

        // TODO: 为啥没有 parallel
        Mono<ArrayList<JSONObject>> pmono = Flux.concat(mono, mono2)
                .parallel(2)
                .runOn(Schedulers.parallel())
                .reduce(ArrayList<JSONObject>::new, (a, j) -> {
                    a.add(j);
                    return a;
                })
                .map(list -> list)
                .reduce((a, b) -> {
                    a.addAll(b);
                    return a;
                });

        return ok().body(pmono, new ParameterizedTypeReference<ArrayList<JSONObject>>() {
        });
    }

    /**
     * 演示异常信息处理
     * <p>
     *  {@link MyErrorAttributes}
     */
    public Mono<ServerResponse> except(ServerRequest req) {
        return ok().body(Mono.just(ExampleDto.newInstance()).map((json) -> {
            throw BizException.badRequestError("想都不用想，定是客户端出了问", new IllegalStateException());
        }), JSONObject.class);
    }

    /**
     * TODO: 响应序列
     * {@link org.springframework.boot.autoconfigure.jackson.JacksonProperties}
     * {@link org.springframework.http.converter.json.Jackson2ObjectMapperBuilder}
     */
    public Mono<ServerResponse> respSerial(ServerRequest req) {
        val json = new JSONObject();
        json.put("date", new Date());
        json.put("localDate", LocalDate.of(2018, 8, 8));
        json.put("string", "Hello world");
        json.put("joda", DateTime.now());
        return ok().syncBody(json);
    }
    
    public Mono<ServerResponse> mysql(ServerRequest req) {
    	return articleRepository.getForReading(req.pathVariable("uniqId"))
    			.map(article -> {
		    		log.info("map data");
		    		val json = new JSONObject();
		    		json.put("title", article.getTitle());
		    		json.put("renderedContent", article.getRenderedContent());
		    		return json;
		    	})
    			.flatMap(json -> ok().body(fromObject(json)))
    			.switchIfEmpty(notFound().build());
    }

    // TODO: 么时候用 mono，什么时候用 flux

    // TODO: 重定
}

@Builder
@Data
class ExampleDto {
    private String hello;

    public static ExampleDto newInstance() {
        return ExampleDto.builder().hello("world").build();
    }
}

@Data
class RequestDto {
    private Integer id;
    private String status;
}